namespace ScrumSprintMonitor.ConfigEditor.Views.SprintConfigurationEditorView
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Globalization;
	using System.Linq;
	using System.Windows;
	using System.Windows.Input;

	using ContinuousLinq.Reactive;

	using Infrastructure;
	using Infrastructure.Configuration;

	using Microsoft.Practices.Composite.Presentation.Commands;

	#endregion

	internal class SprintConfigurationEditorViewModel : ReactiveObject,
	                                                    ISprintConfigurationEditorViewModel
	{
		static SprintConfigurationEditorViewModel()
		{
			var dependsOn = Register<SprintConfigurationEditorViewModel>();

			dependsOn.Call(me => me.PopulateProjectDependentLists()).OnChanged(me => me.TfsProjectName);
			dependsOn.Call(me =>
			               {
			               	me.buildDefinitionStates = null;
			               	me.OnPropertyChanged("BuildDefinitionStates");
			               }).OnChanged(me => me.BuildDefinitions);
			dependsOn.Call(me => me.OnPropertyChanged("CurrentIterationPath")).OnChanged(me => me.IterationPaths);
			dependsOn.Call(me => me.advanceSprintCommand.RaiseCanExecuteChanged()).OnChanged(me => me.CurrentIterationPath);
			dependsOn.Call(me => me.okCommand.RaiseCanExecuteChanged()).OnChanged(me => me.FetchingAvailableTfsProjects).OnChanged(
				me => me.FetchingBuildDefinitions).OnChanged(me => me.FetchingIterationPaths);
		}

		public SprintConfigurationEditorViewModel(IConnectionProvider connectionProvider, ISprintConfiguration configuration,
		                                          IProcessTemplateAdapterProvider processTemplateAdapterProvider)
		{
			this.getProjectListDel = new Action(() =>
			                                    {
			                                    	OnPropertyChanged("FetchingAvailableTfsProjects");
			                                    	SetProjectListAsync(this.connection.GetProjectList());
			                                    });
			this.getBuildDefinitionListDel = new Action(() =>
			                                            {
			                                            	OnPropertyChanged("FetchingBuildDefinitions");
			                                            	SetBuildDefinitionsAsync(this.connection.GetBuildDefinitionList(TfsProjectName));
			                                            });
			this.getIterationPathsDel = new Action(() =>
			                                       {
			                                       	OnPropertyChanged("FetchingIterationPaths");
			                                       	SetIterationPathsAsync(this.connection.GetIterationPaths(TfsProjectName));
			                                       });

			this.connectionProvider = connectionProvider;
			this.configuration = configuration;
			this.processTemplateAdapterProvider = processTemplateAdapterProvider;

			this.advanceSprintCommand = new DelegateCommand<string>(AdvanceSprint, CanAdvanceSprint);
			this.okCommand = new DelegateCommand<Window>(OnOK, CanCloseWithOK);
			this.cancelCommand = new DelegateCommand<Window>(OnCancel, CanCloseWithCancel);
			this.helpCommand = new DelegateCommand<Window>(OnHelp);
		}

		#region Private Methods

		private ICollection<string> BuildDefinitions
		{
			get { return this.buildDefinitions; }
			set
			{
				if (value != this.buildDefinitions)
				{
					this.buildDefinitions = value;

					OnPropertyChanged("BuildDefinitions");
				}
			}
		}

		private void PopulateProjectDependentLists()
		{
			bool projectExists = (this.connection != null && !string.IsNullOrEmpty(TfsProjectName));

			if (FetchingBuildDefinitions)
			{
				this.getBuildDefinitionListDel.EndInvoke(this.getBuildDefinitionListAsyncResult);
			}

			if (FetchingIterationPaths)
			{
				this.getIterationPathsDel.EndInvoke(this.getIterationPathsAsyncResult);
			}

			if (projectExists)
			{
				this.getIterationPathsAsyncResult = this.getIterationPathsDel.BeginInvoke(ar => OnPropertyChanged("FetchingIterationPaths"), null);
				this.getBuildDefinitionListAsyncResult = this.getBuildDefinitionListDel.BeginInvoke(ar => OnPropertyChanged("FetchingBuildDefinitions"), null);
			}
			else
			{
				BuildDefinitions = null;
				IterationPaths = null;
			}
		}

		private void SetProjectListAsync(ICollection<string> collection)
		{
			AvailableTfsProjects = collection;
		}

		private void SetIterationPathsAsync(ICollection<string> collection)
		{
			IterationPaths = collection;

			if (IterationPaths == null || !IterationPaths.Contains(CurrentIterationPath))
			{
				CurrentIterationPath = null;
			}
		}

		private void SetBuildDefinitionsAsync(ICollection<string> collection)
		{
			BuildDefinitions = collection;

			if (BuildDefinitionStates != null)
			{
				if (BuildDefinitions == null || !BuildDefinitions.Union(BuildDefinitionStates.Select(x => x.Name)).SequenceEqual(BuildDefinitions))
				{
					BuildDefinitionStates = null;
				}
			}
		}

		private IBuildDefinitionItemViewModel[] GetViewModelsFromBuildDefinitionNames(IEnumerable<string> buildDefinitionNames)
		{
			if (this.configuration.BuildDefinitionNames == null || BuildDefinitions == null)
			{
				return null;
			}

			var enumerable = from x in BuildDefinitions
			                 select new BuildDefinitionItemViewModel(x)
			                        	{
			                        		IsEnabled = buildDefinitionNames.Contains(x)
			                        	};
			return enumerable.ToArray();
		}

		private static string GetParentIteration(string iterationPath)
		{
			int lastIndex = iterationPath.LastIndexOf('\\');
			return (lastIndex >= 0 ? iterationPath.Substring(0, lastIndex) : string.Empty);
		}

		#endregion

		#region Commands

		private bool CanAdvanceSprint(string sprintsParameter)
		{
			if (IterationPaths == null)
			{
				return false;
			}

			int sprints = Convert.ToInt32(sprintsParameter, CultureInfo.InvariantCulture);
			var iterationPathsList = IterationPaths.ToList();
			int iterationIndex = iterationPathsList.IndexOf(CurrentIterationPath);

			iterationIndex += Math.Sign(sprints);

			return (iterationIndex >= 0 && iterationIndex < iterationPathsList.Count);
		}

		public void AdvanceSprint(string sprintsParameter)
		{
			if (IterationPaths == null)
			{
				return;
			}

			int sprints = Convert.ToInt32(sprintsParameter, CultureInfo.InvariantCulture);
			var newSprintStartDate = SprintStartDate.Value;
			var newSprintEndDate = SprintEndDate.Value;
			var sprintDuration = newSprintEndDate.Add(SprintEndTimeOfDay) - newSprintStartDate.Add(SprintStartTimeOfDay);
			string newIterationPath = null;
			var iterationPathsList = IterationPaths.ToList();
			int iterationIndex = iterationPathsList.IndexOf(CurrentIterationPath);
			int increment = Math.Sign(sprints);

			while (Math.Abs(sprints) > 0)
			{
				sprints -= increment;
				iterationIndex += increment;
				if (iterationIndex < 0 || iterationIndex >= iterationPathsList.Count)
				{
					return;
				}

				if (increment > 0)
				{
					var startDayOfWeek = newSprintStartDate.DayOfWeek;

					for (newSprintStartDate = newSprintEndDate.Date; newSprintStartDate.DayOfWeek != startDayOfWeek;)
					{
						newSprintStartDate = newSprintStartDate.Date.AddDays(1);
					}
					newSprintStartDate = newSprintStartDate.Add(SprintStartTimeOfDay);
					newSprintEndDate = newSprintStartDate + sprintDuration;
				}
				else
				{
					var endDayOfWeek = newSprintEndDate.DayOfWeek;

					for (newSprintEndDate = newSprintStartDate.Date; newSprintEndDate.DayOfWeek != endDayOfWeek;)
					{
						newSprintEndDate = newSprintEndDate.Date.AddDays(-1);
					}
					newSprintEndDate = newSprintEndDate.Add(SprintEndTimeOfDay);
					newSprintStartDate = newSprintEndDate - sprintDuration;
				}
				if (!string.IsNullOrEmpty(CurrentIterationPath))
				{
					newIterationPath = iterationPathsList[iterationIndex];
					if (GetParentIteration(newIterationPath) != GetParentIteration(CurrentIterationPath))
					{
						return;
					}
				}
			}

			SprintStartDate = newSprintStartDate;
			SprintEndDate = newSprintEndDate;
			if (!string.IsNullOrEmpty(newIterationPath))
			{
				CurrentIterationPath = newIterationPath;
			}
		}

		private static bool CanCloseWithCancel(Window configurationWindow)
		{
			return true;
		}

		private static void OnCancel(Window configurationWindow)
		{
			configurationWindow.DialogResult = false;
			configurationWindow.Close();
		}

		private bool CanCloseWithOK(Window configurationWindow)
		{
			return !FetchingBuildDefinitions && !FetchingIterationPaths && !FetchingAvailableTfsProjects;
		}

		private static void OnOK(Window configurationWindow)
		{
			configurationWindow.DialogResult = true;
			configurationWindow.Close();
		}

		private static void OnHelp(Window configurationWindow)
		{
			try
			{
				Process.Start(@"http://sweux.com/blogs/pombeiro/index.php/scrum-sprint-monitor-clickonce-installation/configuration/#SprintConfiguration");
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}

		#endregion

		#region Implementation of ISprintConfigurationEditorViewModel

		public void ChangeServer(string serverName)
		{
			this.connection = this.connectionProvider.ProvideConnectionTo(serverName, true);

			var templates = this.processTemplateAdapterProvider.AvailableProcessTemplates.Select(x => x.ProcessTemplateName).ToList();
			templates.Sort();

			if (FetchingAvailableTfsProjects)
			{
				this.getProjectListDel.EndInvoke(this.getProjectListAsyncResult);
			}

			this.getProjectListAsyncResult = this.getProjectListDel.BeginInvoke(ar => OnPropertyChanged("FetchingAvailableTfsProjects"), null);

			AvailableTfsProcessTemplates = templates;

			TfsProjectName = this.configuration.ProjectName;
			TfsProcessTemplateName = this.configuration.ProcessTemplateName;
			CurrentIterationPath = (this.configuration.IterationPath == null ? null : this.configuration.IterationPath.Remove(0, this.tfsProjectName.Length));
			SprintStartDate = this.configuration.SprintStartDate.Date;
			SprintStartTimeOfDay = this.configuration.SprintStartDate.TimeOfDay;
			SprintStartTimeZone = this.configuration.SprintStartTimeZone;
			SprintEndDate = this.configuration.SprintEndDate.Date;
			SprintEndTimeOfDay = this.configuration.SprintEndDate.TimeOfDay;
			SprintEndTimeZone = this.configuration.SprintEndTimeZone;
			ExcludeWeekends = this.configuration.ExcludeWeekends;
		}

		public ICollection<string> AvailableTfsProjects
		{
			[DebuggerStepThrough]
			get { return this.availableTfsProjects; }
			internal set
			{
				if (value != this.availableTfsProjects)
				{
					this.availableTfsProjects = value;

					OnPropertyChanged("AvailableTfsProjects");
				}
			}
		}

		public bool FetchingAvailableTfsProjects
		{
			[DebuggerStepThrough]
			get { return this.getProjectListAsyncResult != null && !this.getProjectListAsyncResult.IsCompleted; }
		}

		public string TfsProjectName
		{
			[DebuggerStepThrough]
			get { return this.tfsProjectName; }
			set
			{
				if (value != this.tfsProjectName)
				{
					this.tfsProjectName = value;

					OnPropertyChanged("TfsProjectName");
				}
			}
		}

		public ICollection<string> AvailableTfsProcessTemplates
		{
			[DebuggerStepThrough]
			get { return this.availableTfsProcessTemplates; }
			set
			{
				if (value != this.availableTfsProcessTemplates)
				{
					this.availableTfsProcessTemplates = value;

					OnPropertyChanged("AvailableTfsProcessTemplates");
				}
			}
		}

		public string TfsProcessTemplateName
		{
			[DebuggerStepThrough]
			get { return this.tfsProcessTemplateName; }
			set
			{
				if (value != this.tfsProcessTemplateName)
				{
					this.tfsProcessTemplateName = value;

					OnPropertyChanged("TfsProcessTemplateName");
				}
			}
		}

		public string CurrentIterationPath
		{
			[DebuggerStepThrough]
			get { return this.currentIterationPath; }
			set
			{
				if (value != this.currentIterationPath)
				{
					this.currentIterationPath = value;

					OnPropertyChanged("CurrentIterationPath");
				}
			}
		}

		public IBuildDefinitionItemViewModel[] BuildDefinitionStates
		{
			get
			{
				if (this.buildDefinitionStates == null)
				{
					BuildDefinitionStates = GetViewModelsFromBuildDefinitionNames(this.configuration.BuildDefinitionNames);
				}
				return this.buildDefinitionStates;
			}
			private set
			{
				if (value != this.buildDefinitionStates)
				{
					this.buildDefinitionStates = value;

					OnPropertyChanged("BuildDefinitionStates");
				}
			}
		}

		public DateTime? SprintStartDate
		{
			[DebuggerStepThrough]
			get { return this.sprintStartDate.Date; }
			set
			{
				if (value.Value != this.sprintStartDate)
				{
					this.sprintStartDate = value.Value.Date.Add(this.sprintStartDate.TimeOfDay);

					OnPropertyChanged("SprintStartDate");
				}
			}
		}

		public TimeSpan SprintStartTimeOfDay
		{
			[DebuggerStepThrough]
			get { return this.sprintStartDate.TimeOfDay; }
			set
			{
				if (value != this.sprintStartDate.TimeOfDay)
				{
					if (value.TotalDays >= 1.0)
					{
						throw new ArgumentOutOfRangeException("value");
					}

					this.sprintStartDate = this.sprintStartDate.Date.Add(value);

					OnPropertyChanged("SprintStartTimeOfDay");
				}
			}
		}

		public TimeZoneInfo SprintStartTimeZone
		{
			[DebuggerStepThrough]
			get { return this.sprintStartTimeZone; }
			set
			{
				if (value != this.sprintStartTimeZone)
				{
					this.sprintStartTimeZone = value;

					OnPropertyChanged("SprintStartTimeZone");
				}
			}
		}

		public DateTime? SprintEndDate
		{
			[DebuggerStepThrough]
			get { return this.sprintEndDate.Date; }
			set
			{
				if (value.Value != this.sprintEndDate.Date)
				{
					this.sprintEndDate = value.Value.Date.Add(this.sprintEndDate.TimeOfDay);

					OnPropertyChanged("SprintEndDate");
				}
			}
		}

		public TimeSpan SprintEndTimeOfDay
		{
			[DebuggerStepThrough]
			get { return this.sprintEndDate.TimeOfDay; }
			set
			{
				if (value != this.sprintEndDate.TimeOfDay)
				{
					if (value.TotalDays >= 1.0)
					{
						throw new ArgumentOutOfRangeException("value");
					}

					this.sprintEndDate = this.sprintEndDate.Date.Add(value);

					OnPropertyChanged("SprintEndTimeOfDay");
				}
			}
		}

		public TimeZoneInfo SprintEndTimeZone
		{
			[DebuggerStepThrough]
			get { return this.sprintEndTimeZone; }
			set
			{
				if (value != this.sprintEndTimeZone)
				{
					this.sprintEndTimeZone = value;

					OnPropertyChanged("SprintEndTimeZone");
				}
			}
		}

		public ICommand AdvanceSprintCommand
		{
			[DebuggerStepThrough]
			get { return this.advanceSprintCommand; }
		}

		public bool ExcludeWeekends
		{
			[DebuggerStepThrough]
			get { return this.excludeWeekends; }
			set
			{
				if (value != ExcludeWeekends)
				{
					this.excludeWeekends = value;

					OnPropertyChanged("ExcludeWeekends");
				}
			}
		}

		public bool IsReadOnly
		{
			[DebuggerStepThrough]
			get { return this.configuration.IsReadOnly; }
		}

		public void Save()
		{
			this.configuration.ProjectName = this.tfsProjectName;
			this.configuration.ProcessTemplateName = this.tfsProcessTemplateName;
			this.configuration.IterationPath = string.Format(@"{0}{1}", this.tfsProjectName, this.currentIterationPath);
			this.configuration.BuildDefinitionNames = (this.buildDefinitionStates != null
			                                           	? this.buildDefinitionStates.Where(x => x.IsEnabled).Select(x => x.Name).ToArray()
			                                           	: null);
			this.configuration.SprintStartDate = this.sprintStartDate;
			this.configuration.SprintStartTimeZone = this.sprintStartTimeZone;
			this.configuration.SprintEndDate = this.sprintEndDate;
			this.configuration.SprintEndTimeZone = this.sprintEndTimeZone;
			this.configuration.ExcludeWeekends = this.excludeWeekends;

			this.configuration.Save();
		}

		public ICommand OkCommand
		{
			[DebuggerStepThrough]
			get { return this.okCommand; }
		}

		public ICommand CancelCommand
		{
			[DebuggerStepThrough]
			get { return this.cancelCommand; }
		}

		public ICommand HelpCommand
		{
			[DebuggerStepThrough]
			get { return this.helpCommand; }
		}

		public ICollection<string> IterationPaths
		{
			[DebuggerStepThrough]
			get { return this.iterationPaths; }
			set
			{
				if (value != this.iterationPaths)
				{
					this.iterationPaths = value;

					OnPropertyChanged("IterationPaths");
				}
			}
		}

		public bool FetchingIterationPaths
		{
			[DebuggerStepThrough]
			get { return this.getIterationPathsAsyncResult != null && !this.getIterationPathsAsyncResult.IsCompleted; }
		}

		public bool FetchingBuildDefinitions
		{
			[DebuggerStepThrough]
			get { return this.getBuildDefinitionListAsyncResult != null && !this.getBuildDefinitionListAsyncResult.IsCompleted; }
		}

		public ICollection<TimeZoneInfo> TimeZones
		{
			[DebuggerStepThrough]
			get { return this.timeZones; }
		}

		#endregion

		#region Private Fields

		private readonly DelegateCommand<string> advanceSprintCommand;
		private readonly ICommand cancelCommand;
		private readonly ISprintConfiguration configuration;
		private readonly IConnectionProvider connectionProvider;
		private readonly Action getBuildDefinitionListDel;
		private readonly Action getIterationPathsDel;
		private readonly Action getProjectListDel;
		private readonly ICommand helpCommand;
		private readonly DelegateCommand<Window> okCommand;
		private readonly IProcessTemplateAdapterProvider processTemplateAdapterProvider;
		private readonly ICollection<TimeZoneInfo> timeZones = TimeZoneInfo.GetSystemTimeZones();

		private ICollection<string> availableTfsProcessTemplates = new List<string>();
		private ICollection<string> availableTfsProjects;
		private ICollection<string> buildDefinitions;
		private IBuildDefinitionItemViewModel[] buildDefinitionStates;
		private IConnection connection;
		private string currentIterationPath;
		private bool excludeWeekends;
		private IAsyncResult getBuildDefinitionListAsyncResult;
		private IAsyncResult getIterationPathsAsyncResult;
		private IAsyncResult getProjectListAsyncResult;
		private ICollection<string> iterationPaths;
		private DateTime sprintEndDate;
		private TimeZoneInfo sprintEndTimeZone;
		private DateTime sprintStartDate;
		private TimeZoneInfo sprintStartTimeZone;
		private string tfsProcessTemplateName;
		private string tfsProjectName;

		#endregion
	}
}